﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Swift;
using System.Reflection;

public class Graph : SerializableData
{
    List<Unit> units = new List<Unit>();

    public void AddUnit(Unit u)
    {
        units.Add(u);
    }

    public void RemoveUnit(Unit u)
    {
        units.Remove(u);
    }

    public Unit[] AllUnits
    {
        get
        {
            return units.ToArray();
        }
    }

    public void SetConnection(Unit src, Unit dst)
    {
        var d = 0f;
        var ct = GetConnection(src, dst, out d);
        if (ct == ConnectionType.None)
            return;

        src.LinkChild(dst, ct);
        dst.Update(0, ct);
    }

    public ConnectionType GetConnection(Unit src, Unit dst, out float d)
    {
        d = 0;

        if (src is PowerSource)
            return PowerSourceAndUnit(src as PowerSource, dst, out d);
        else if (dst is PowerSource)
            return ConnectionType.None;
        else if (src is Gear && dst is Gear)
            return GearAndGear(src as Gear, dst as Gear, out d);
        else if (src is Gear && dst is Stick)
            return GearAndStick(src as Gear, dst as Stick, out d);

        return ConnectionType.None;
    }

    #region save and load graph

    protected override void Sync()
    {
        if (IsWrite)
            WriteGraph(W);
        else
            ReadGraph(R);
    }

    void WriteGraph(IWriteableBuffer w)
    {
        var allUs = AllUnits;
        w.Write(allUs.Length);
        for (var i = 0; i < allUs.Length; i++)
        {
            var u = allUs[i];
            w.Write(u.GetType().FullName);
            u.Serialize(w);
        }

        for (var i = 0; i < allUs.Length; i++)
        {
            var u = allUs[i];
            var cnt = u.Dst.Count;
            w.Write(cnt);
            foreach (var dst in u.Dst.Keys)
            {
                var ct = (int)u.Dst[dst];
                w.Write(allUs.FirstIndexOf(dst));
                w.Write(ct);
            }
        }
    }

    void ReadGraph(IReadableBuffer r)
    {
        var cnt = r.ReadInt();
        var allUs = new Unit[cnt];
        for (var i = 0; i < cnt; i++)
        {
            var type = r.ReadString();
            var u = Assembly.GetExecutingAssembly().CreateInstance(type) as Unit;
            u.Deserialize(r);
            allUs[i] = u;
        }

        for (var i = 0; i < cnt; i++)
        {
            var u = allUs[i];
            var dstCnt = r.ReadInt();
            for (var j = 0; j < dstCnt; j++)
            {
                var n = r.ReadInt();
                var dst = allUs[n];
                var ct = (ConnectionType)r.ReadInt();
                u.LinkChild(dst, ct);
            }
        }
    }

    #endregion

    #region all kinds of connections

    readonly float connectableThreshold = 5;
    public Unit FindNearestConnectableSrc(Unit dst, Func<Unit, bool> filter = null)
    {
        if (dst.Src != null)
            return dst.Src;

        var minD = connectableThreshold;
        Unit nearestSrc = null;
        foreach (var src in units)
        {
            if (src == dst || dst.HasChildren(src) || (filter != null && !filter(src)))
                continue;

            var d = 0f;
            var ct = GetConnection(src, dst, out d);
            if (ct != ConnectionType.None && d < minD)
            {
                nearestSrc = src;
                minD = d;
            }
        }

        return nearestSrc;
    }

    ConnectionType PowerSourceAndUnit(PowerSource ps, Unit u, out float d)
    {
        d = (ps.Pos - u.Pos).magnitude;
        return ConnectionType.Concentric;
    }

    ConnectionType GearAndGear(Gear g1, Gear g2, out float d)
    {
        d = (g1.Pos - g2.Pos).magnitude;
        if (d < g1.Radius + g2.Radius)
        {
            d = Mathf.Abs(g1.Radius - g2.Radius);
            return ConnectionType.Concentric;
        }
        else
        {
            d = d - g1.Radius - g2.Radius;
            return ConnectionType.Clenching;
        }
    }

    ConnectionType GearAndStick(Gear g, Stick s, out float d)
    {
        var dv = s.Pos - g.Pos;
        d = dv.magnitude;
        if (d < g.Radius)
            return ConnectionType.Concentric;
        else
        {
            if (s.Angle == 0) // vertical
            {
                if (Mathf.Abs(dv.y) < g.Radius)
                {
                    d = d - g.Radius;
                    return ConnectionType.Clenching | (dv.x < 0 ? ConnectionType.OnLeftFixY : ConnectionType.OnRightFixY);
                }
                else if (Mathf.Abs(dv.x) < g.Radius)
                {
                    d = d - g.Radius - s.Length / 2;
                    return ConnectionType.Clenching | (dv.y > 0 ? ConnectionType.OnTopFixX : ConnectionType.OnBottomFixX);
                }
                else
                    return ConnectionType.None;
            }
            else // horizontal
            {
                if (Mathf.Abs(dv.x) < g.Radius)
                {
                    d = d - g.Radius;
                    return ConnectionType.Clenching | (dv.y > 0 ? ConnectionType.OnTopFixX : ConnectionType.OnBottomFixX);
                }
                else if (Mathf.Abs(dv.y) < g.Radius)
                {
                    d = d - g.Radius - s.Length / 2;
                    return ConnectionType.Clenching | (dv.x < 0 ? ConnectionType.OnLeftFixY : ConnectionType.OnRightFixY);
                }
                else
                    return ConnectionType.None;
            }
        }
    }

    #endregion
}
